<?php

class XMLImporter
{
    // Database configuration
    private $dbConfig = Array();
    private $fieldsMap = Array();
    private $connection;
    private $xmlFilePath;
    private $xml;
    private $downloadFilesFolder;
    private $rootItem;

    const ERROR_DATABASE_CONFIGURATION_NOT_PROVIDED = "Database configuration is not provided for XMLImporter!";
    const ERROR_XML_DATABASE_FIELDS_MAP_NOT_PROVIDED = "XML to database fields map is not provided for XMLImporter!";
    const ERROR_XML_FILE_PATH_NOT_PROVIDED = "XML file path is not provided for XMLImporter!";
    const ERROR_XML_ROOT_ITEM_NOT_PROVIDED = "XML file root item name is not provided!";

    public function __construct($dbConfig, $fieldsMap, $xmlFilePath, $downloadFilesFolder, $rootItem)
    {
        if (isset($dbConfig)) {
            $this->dbConfig = $dbConfig;
            $this->connectToDatabase();
        } else {
            die(self::ERROR_DATABASE_CONFIGURATION_NOT_PROVIDED);
        }

        if (isset($fieldsMap)) {
            $this->fieldsMap = $fieldsMap;
        } else {
            die(self::ERROR_XML_DATABASE_FIELDS_MAP_NOT_PROVIDED);
        }

        if (isset($xmlFilePath)) {
            $this->xmlFilePath = $xmlFilePath;
            $this->loadXMLFile();
        } else {
            die(self::ERROR_XML_FILE_PATH_NOT_PROVIDED);
        }

        if (isset($downloadFilesFolder)) {
            $this->downloadFilesFolder = $downloadFilesFolder;
        }

        if (isset($rootItem)) {
            $this->rootItem= $rootItem;
        } else {
            die(self::ERROR_XML_ROOT_ITEM_NOT_PROVIDED);
        }
    }

    private function connectToDatabase()
    {
        // Create Database connection
        $this->connection = mysqli_connect($this->dbConfig["host"], $this->dbConfig["user"], $this->dbConfig["password"], $this->dbConfig["database"]);

        // Check connection
        if (mysqli_connect_errno()) {
            die("Failed to connect to MySQL: " . mysqli_connect_error());
        }
    }

    public function parseFile($table, $idName)
    {
        $fields = array_keys($this->fieldsMap);
        $sql = $this->getBaseInsertStatement($table, $fields);

        $xmlFields = array_values($this->fieldsMap);
        $var = $this->getProductList($this->xml, $this->rootItem);
        $productList = $var;
        $uniqueItemsWrapper = array();
        foreach ($productList as $product) {
            $itemWrapper = new ItemWrapper($product, null);
            $item = $this->toString($itemWrapper, $xmlFields);
            $uniqueItemsWrapper[$item] = $itemWrapper;
        }

        $uniqueItemsWrapper = array_values($uniqueItemsWrapper);

        $sql .= $this->parseItems($uniqueItemsWrapper, $this->fieldsMap);

        $this->executeUpdate($sql, $this->fieldsMap);

        $firstItemId = mysqli_insert_id($this->connection);
        $lastItemId = $firstItemId + mysqli_affected_rows($this->connection) - 1;

        $addedItems = $this->getAddedItems($table, $idName, $firstItemId, $lastItemId);
        return array($this->getItemWrappers($uniqueItemsWrapper, $addedItems, $this->getKey($this->fieldsMap)), $firstItemId, $lastItemId);
    }

    private function getAddedItems($table, $idName, $firstItemId, $lastItemId)
    {
        $sql = "SELECT * FROM $table WHERE $idName BETWEEN $firstItemId AND $lastItemId";
        return $this->executeQuery($sql);
    }

    private function getItemWrappers($uniqueItemsWrapper, $addedItems, $key)
    {
        $result = array();
        while ($row = mysqli_fetch_assoc($addedItems)) {
            $result[] = $this->getXMLItem($uniqueItemsWrapper, $row[$key], $key);
        }
        return $result;
    }

    private function getXMLItem($uniqueItemsWrapper, $databaseItemValue, $key)
    {
        foreach ($uniqueItemsWrapper as $item) {
            $itemValue = $item->fields[$key];
            if ($itemValue->getValue() == $databaseItemValue) {
                return $item;
            }
        }
    }

    private function getKey($fieldsMap)
    {
        foreach ($fieldsMap as $databaseFiled => $xmlField) {
            if (array_key_exists("key", $xmlField)) {
                return $databaseFiled;
            }
        }
    }

    /**
     * @param $uniqueItemsWrapper
     * @param $fieldsMap
     * @return string
     */
    private function parseItems($uniqueItemsWrapper, $fieldsMap)
    {
        $sql = "";
        foreach ($uniqueItemsWrapper as $itemWrapper) {
            $fields = array();
            foreach ($fieldsMap as $databaseFiled => $xmlField) {
                $value = $this->getValue($itemWrapper, $xmlField);
                $dependsOn = array_key_exists("dependsOn", $xmlField) ? $xmlField["dependsOn"] : null;
                $fields[$databaseFiled] = new Field($databaseFiled, $value, $dependsOn);
            }

            $data = $this->processDependencies($fields);

            $sql .= "(" . join(", ", $data) . "),\n";

            $itemWrapper->fields = $fields;
        }
        return $sql;
    }

    private function executeQuery($query)
    {
        $result = mysqli_query($this->connection, $query);
        if (!$result) {
            die('Error: ' . mysqli_error($this->connection));
        }
        return $result;
    }

    private function loadXMLFile()
    {
		$xml = simplexml_load_file($this->xmlFilePath);
        if ($xml === false) {
            echo "Failed loading XML</br>";
            $this->showErrors();
        } else {
            $this->xml = $xml;
            echo "File " . $xml->getName() . " has been loaded</br>";
        }
		/*$filePath = $this->xmlFilePath;
		if(filter_var($filePath, FILTER_VALIDATE_URL)){ 
			$pathinfo = pathinfo(parse_url($filePath, PHP_URL_PATH));
			if ($pathinfo["extension"] == ".gz") {
				$tmpFile = uniqid("xml_"). "." . $pathinfo["extension"];
				$file = $this->downloadFilesFolder . $tmpFile;
				if (!file_put_contents($file, file_get_contents(urldecode($result))))
				{
					die("Can't download file " . $filePath);
				}
				// Raising this value may increase performance
				$buffer_size = 8192; // read 8kb at a time
				$out_file_name = $this->downloadFilesFolder . str_replace('.gz', '', $tmpFile); 
				// Open our files (in binary mode)
				$file = gzopen($file, 'rb');
				$out_file = fopen($out_file_name, 'wb'); 
				// Keep repeating until the end of the input file
				while(!gzeof($file)) {
				// Read buffer-size bytes
				// Both fwrite and gzread and binary-safe
				  fwrite($out_file, gzread($file, $buffer_size));
				}  
				// Files are done, close files
				fclose($out_file);
				gzclose($file);
				$this->xmlFilePath = $out_file;
			}
		}
		
		$xml = simplexml_load_file($this->xmlFilePath);
		if ($xml === false) {
			echo "Failed loading XML</br>";
			$this->showErrors();
		} else {
			$this->xml = $xml;
			echo "File " . $xml->getName() . " has been loaded</br>";
		}*/
    }

    private function showErrors()
    {
        foreach (libxml_get_errors() as $error) {
            echo "\t", $error->message;
        }
    }

    private function getValue($itemWrapper, $xmlField)
    {
        $type = $xmlField["type"];
        $name = $xmlField["value"];
        $default = array_key_exists("default", $xmlField) ? $xmlField["default"] : null;
        $action = array_key_exists("action", $xmlField) ? $xmlField["action"] : null;
        $itemIndex = array_key_exists("index", $xmlField) ? $xmlField["index"] : null;
        $var = null;
        if (!strpos($name, "->")) {
            $var = $itemWrapper->item;
        } else {
            $explode = explode("->", $name);
            for ($i = 0; $i < count($explode) - 1; $i++) {
                $var = isset($var) ? $var->$explode[$i] : $itemWrapper->item->$explode[$i];
            }
            $name = $explode[count($explode) - 1];
        }
        $result = $this->getDefaultValue($this->getValueByType($var, $name, $type, $itemIndex), $default);
        if (isset($action)) {
            $var1 = $this->$action($result, $itemWrapper);
            return preg_replace('/\p{C}+/u', "", $var1);
        }
        return preg_replace('/\p{C}+/u', "", $result);
    }

    // Move callbacks to separate class!
    public function getCategoryByTitle($title, $itemWrapper)
    {
        $result = $this->executeQuery("SELECT cat_id FROM tb_category WHERE cat_name = '$title'");
        if ($result->num_rows > 0) {
            $categoryRow = $result->fetch_assoc();
            return $categoryRow['cat_id'];
        }
        $this->executeQuery("INSERT INTO tb_category (cat_name, parent_id, active, cat_keyword) VALUES ('$title', 0, 1, '')");
        return mysqli_insert_id($this->connection);
    }

    public function getSubcategoryByTitle($title, $itemWrapper)
    {
        $primaryCategory = $this->getValue($itemWrapper, Array(Type::TYPE => ValueType::TAG,
            "value" => "category->primary",
            "action" => 'getCategoryByTitle'));

        $result = $this->executeQuery("SELECT cat_id FROM tb_category WHERE cat_name = '$title' AND parent_id = '$primaryCategory'");
        if ($result->num_rows > 0) {
            $categoryRow = $result->fetch_assoc();
            return $categoryRow['cat_id'];
        }
        $this->executeQuery("INSERT INTO tb_category (cat_name, parent_id, active, cat_keyword) VALUES ('$title', '$primaryCategory', 1, '')");
        return mysqli_insert_id($this->connection);
    }

    public function getGrouponSubcategoryByTitle($title, $itemWrapper)
    {
        $primaryCategory = $this->getValue($itemWrapper, Array(Type::TYPE => ValueType::TAG_INDEX,
            "value" => "dealTypes->dealType->id",
            "index" => "0",
            "action" => "getCategoryByTitle"));

        $result = $this->executeQuery("SELECT cat_id FROM tb_category WHERE cat_name = '$title' AND parent_id = '$primaryCategory'");
        if ($result->num_rows > 0) {
            $categoryRow = $result->fetch_assoc();
            return $categoryRow['cat_id'];
        }
        $this->executeQuery("INSERT INTO tb_category (cat_name, parent_id, active, cat_keyword) VALUES ('$title', '$primaryCategory', 1, '')");
        return mysqli_insert_id($this->connection);
    }

	public function getAffilateLink($result, $itemWrapper)
    {
		$disallowed = array('http://', 'https://');
		foreach($disallowed as $item) {
			if(strpos($result, $item) === 0) {
				return str_replace($item, '', $result);
			}
		}
		return $result;
    }

    private function toString($item, $fields)
    {
        return uniqid();
        $result = "";
        foreach ($fields as $field) {
            $result .= $this->getValue($item, $field);
        }
        return $result;
    }

    /**
     * @param $fieldsMap
     * @internal param $xmlFields
     * @return mixed
     */
    private function getSimpleName($fieldsMap)
    {
        foreach ($fieldsMap as $databaseField => $xmlField) {
            return $databaseField . " = " . $databaseField;
        }
        return "";
    }

    private function getValueByType($item, $name, $type, $index = NULL)
    {
        if ($type == ValueType::ATTRIBUTE) {
            return $item[$name];
        }
        if ($type == ValueType::TAG) {
            return $item->$name;
        }
        if ($type == ValueType::TAG_INDEX) {
            $i = 0;
            foreach ($item as $itemNum) {
                if ($i == $index) {
                    return $itemNum->$name;
                }
                $i++;
            }
            return $item->$name[$index];
        }
        if ($type == ValueType::VALUE) {
            return $name;
        }
    }

    /**
     * @param $fields
     * @return string
     */
    private function getBaseInsertStatement($table, $fields)
    {
        return "INSERT INTO " . $table . " (" . join(', ', $fields) . ") VALUES ";
    }

    /**
     * @return string
     */
    private function getFinishInsertStatement($fieldsMap)
    {
        return " ON DUPLICATE KEY UPDATE " . $this->getSimpleName($fieldsMap);
    }

    public function mapAdditionalTable($uniqueItemsWrapper, $firstItemId, $lastItemId, $fieldsMap, $table)
    {
        echo "Mapping additional table: $table \n";
        echo "First item ID: $firstItemId \n";
        echo "Last item ID: $lastItemId \n";
//        print_r($uniqueItemsWrapper);

        if ($lastItemId == -1) {
            return;
        }
        $fields = array_keys($fieldsMap);
        $sql = $this->getBaseInsertStatement($table, $fields);

        for ($i = $firstItemId, $j = 0; $i <= $lastItemId; $i++, $j++) {
            $uniqueItemsWrapper[$j]->fields["sale_id"] = new Field("sale_id", $i, NULL);
        }

        $sql .= $this->parseItems($uniqueItemsWrapper, $fieldsMap);

        $this->executeUpdate($sql, $fieldsMap);
    }

    public function copyArray($array)
    {
        $arrayCopy = array();
        foreach ($array as $key => $value) {
            $arrayCopy[$key] = clone $value;
        }
        return $arrayCopy;
    }

    protected function getProductList($xml, $rootItem)
    {
        if (!strpos($rootItem, "->")) {
            return $xml->$rootItem;
        } else {
            $explode = explode("->", $rootItem);
            for ($i = 0; $i < count($explode); $i++) {
                $var = isset($var) ? $var->$explode[$i] : $xml->$explode[$i];
            }
            return $var;
        }
    }

    private function getSaleIdFromItem($result, $itemWrapper)
    {
        return $itemWrapper->fields["sale_id"]->getValue();
    }

    private function getCategoryIdFromItem($result, $itemWrapper)
    {
        return $itemWrapper->fields["category_id"]->getValue();
    }

    private function downloadImage($result, $itemWrapper) {
        echo "Downloading image: " . $result . "\n";
        $pathinfo = pathinfo(parse_url($result, PHP_URL_PATH));
		$imageName = uniqid("img_") . "." . $pathinfo["extension"];
        $img = $this->downloadFilesFolder . $imageName;
        if (!file_put_contents(urldecode($img), file_get_contents($result)))
        {
            return "";
        };
        return $imageName;
    }

    private function processDate($result, $itemWrapper) {
        if (strpos($result, "T")) {
            return substr($result, 0, 10);
        }
        return $result;
    }

    private function getFileExtension($result, $itemWrapper) {
        $pathinfo = pathinfo(parse_url($result, PHP_URL_PATH));
        return $pathinfo["extension"];
    }

    /**
     * @param $items
     * @return array
     */
    private function processDependencies($items)
    {
        $data = array();
        foreach ($items as $databaseField => $item) {
            if (null !== $item->getDependsOn()) {
                $value = $this->processItem($item, $items);
            }
            $data[] = "'" . mysqli_real_escape_string($this->connection, trim($item->getValue())) . "'";
        }
        return $data;
    }

    private function processItem($dependingItem, $items)
    {
        foreach ($items as $item) {
            if ($dependingItem->getDependsOn() == $item->getDatabaseField()) {
                $dependingItem->setValue($this->getValueBasedOnDependencyField($dependingItem, $item));
            }
        }
        return $dependingItem->getValue();
    }

    private function getValueBasedOnDependencyField($dependingItem, $dependentItem)
    {
        $databaseField = $dependingItem->getDatabaseField();
        if ($databaseField == "sales_desc") {
            return $dependingItem->getValue() . "<br><br>Price:$" . $dependentItem->getValue() . "<retail>";
        } else if ($databaseField == "not_sure") {
            if (null == $dependentItem->getValue() || trim($dependentItem->getValue()) == "") {
                return "1";
            }
        }
        return $dependingItem->getValue();
    }

    private function getDefaultValue($value, $default)
    {
        if (isset($value) && trim($value) != "") {
            return $value;
        }
        return $default;
    }

    /**
     * @param $sql
     * @return string
     */
    private function executeUpdate($sql, $fieldsMap)
    {
        $sql = substr($sql, 0, -2);
        $sql .= $this->getFinishInsertStatement($fieldsMap);
        echo $sql . "\n";
        $this->executeQuery($sql);
        return $sql;
    }
}

class Field
{
    private $databaseField;
    private $value;
    private $dependsOn;

    function __construct($databaseField, $value, $dependsOn)
    {
        $this->databaseField = $databaseField;
        $this->value = $value;
        $this->dependsOn = $dependsOn;
    }

    /**
     * @return mixed
     */
    public function getDatabaseField()
    {
        return $this->databaseField;
    }

    /**
     * @param mixed $databaseField
     */
    public function setDatabaseField($databaseField)
    {
        $this->databaseField = $databaseField;
    }

    /**
     * @return mixed
     */
    public function getDependsOn()
    {
        return $this->dependsOn;
    }

    /**
     * @param mixed $dependsOn
     */
    public function setDependsOn($dependsOn)
    {
        $this->dependsOn = $dependsOn;
    }

    /**
     * @return mixed
     */
    public function getValue()
    {
        return $this->value;
    }

    /**
     * @param mixed $value
     */
    public function setValue($value)
    {
        $this->value = $value;
    }
}

class ValueType
{
    const VALUE = "value";
    const ATTRIBUTE = "attribute";
    const TAG = "tag";
    const TAG_INDEX = "tag_index";
}

class Type
{
    const VALUE = "value";
    const TYPE = "type";
    const DEPENDS_ON = "dependsOn";
}
//you can map column to some static value like ""
class Constants
{
    const FIELDS = "fields";
    const TBL_SALES = "tbl_sales";
    const TB_SALES_IMAGE = "tb_sales_image";
    const IMAGES_FOLDER = './images/';
    const TBL_SALES_ID = "sales_id";
    const TB_SALES_CATEGORY = "tb_sales_category";
}

class ItemWrapper
{
    public $item;
    public $fields;

    function __construct($item, $fields)
    {
        $this->item = $item;
        $this->fields = $fields;
    }

    /**
     * @param mixed $fields
     */
    public function setFields($fields)
    {
        $this->fields = $fields;
    }
}

?>